#include <stdint.h>
#include <stdfunc.h>
#include <memory.h>
#include <text_video.h>
#include <task.h>
#include <smbios.h>

static uint8_t smbios_checksum(const uint32_t start, const uint32_t count)
{   
    uint32_t i;
    uint8_t chksum;
    
    chksum = 0;
    for(i = 0; i < count; i++)
    {
        chksum = (uint8_t)(chksum + *(uint8_t*)(start + i));
    }
    
    return chksum;
}


static char * smbios_get_string(uint32_t location, uint8_t id)
{
    char * buf;
    uint8_t counter;

    if(!id)
    {return 0;}

    counter = 1;

    while(1)
    {
        if(id == counter)
        {
            buf = (char *)(memalloc(strlen((char *)location) + 1, 0));
            copy_memory((uint8_t*)buf, (uint8_t*)(location), strlen((char *)location) + 1);
            return buf;
        }
        
        location += strlen((char *)location) + 1;
        counter++;
    
        if(!(*(uint8_t*)location)){break;}
    }
    
    return 0;
}

static void parse_smbios_memdev(uint32_t location)
{
    copy_memory((uint8_t*)&sm_memdev[sm_memdev_count-1], (uint8_t*)(location + 4), (uint32_t)(sm_hdr.length - 4));
    
    location += sm_hdr.length;
    
    sm_memdev[sm_memdev_count-1].device_locator_s = smbios_get_string(location, sm_memdev[sm_memdev_count-1].device_locator);
    sm_memdev[sm_memdev_count-1].bank_locator_s = smbios_get_string(location, sm_memdev[sm_memdev_count-1].bank_locator);
    sm_memdev[sm_memdev_count-1].manufacturer_s = smbios_get_string(location, sm_memdev[sm_memdev_count-1].manufacturer);
    sm_memdev[sm_memdev_count-1].serial_s = smbios_get_string(location, sm_memdev[sm_memdev_count-1].serial);
    sm_memdev[sm_memdev_count-1].asset_s = smbios_get_string(location, sm_memdev[sm_memdev_count-1].asset);
    sm_memdev[sm_memdev_count-1].part_s = smbios_get_string(location, sm_memdev[sm_memdev_count-1].part);
 
    if(sm_memdev[sm_memdev_count-1].size)
    {sm_memdev_filled++;}
 
    /*
    if(sm_memdev[sm_memdev_count-1].manufacturer_s){
    printf("    manufacturer = %s \n", sm_memdev[sm_memdev_count-1].manufacturer_s);}
    
    if(sm_memdev[sm_memdev_count-1].size > 0 && sm_memdev[sm_memdev_count-1].size < 0xFFFF)
    {
        printf("    size = %u", sm_memdev[sm_memdev_count-1].size);
        if(sm_memdev[sm_memdev_count-1].size)
        {printf("MB\n");}
        else{printf("KB\n");}
    }

    if(sm_memdev[sm_memdev_count-1].speed)
    {printf("    speed = %uMHz\n", sm_memdev[sm_memdev_count-1].speed);}

    if(sm_memdev[sm_memdev_count-1].device_locator_s){
    printf("    device = %s \n", sm_memdev[sm_memdev_count-1].device_locator_s);}   
    if(sm_memdev[sm_memdev_count-1].bank_locator_s){
    printf("    bank = %s \n", sm_memdev[sm_memdev_count-1].bank_locator_s);}
    if(sm_memdev[sm_memdev_count-1].serial_s){
    printf("    serial = %s \n", sm_memdev[sm_memdev_count-1].serial_s);}
    if(sm_memdev[sm_memdev_count-1].asset_s){
    printf("    asset = %s \n", sm_memdev[sm_memdev_count-1].asset_s);}
    if(sm_memdev[sm_memdev_count-1].part_s){
    printf("    part = %s \n", sm_memdev[sm_memdev_count-1].part_s);}
    */
}

static void parse_smbios_slot(uint32_t location)
{
    copy_memory((uint8_t*)&sm_slot[sm_slot_count-1], (uint8_t*)(location + 4), (uint32_t)(sm_hdr.length - 4));
    location += sm_hdr.length;
    
    sm_slot[sm_slot_count-1].designation_s = smbios_get_string(location, sm_slot[sm_slot_count-1].designation);
}

static void parse_smbios_port(uint32_t location)
{
    copy_memory((uint8_t*)&sm_port[sm_port_count-1], (uint8_t*)(location + 4), (uint32_t)(sm_hdr.length - 4));
    location += sm_hdr.length;
    
    sm_port[sm_port_count-1].internal_reference_designator_s = smbios_get_string(location, sm_port[sm_port_count-1].internal_reference_designator);
    sm_port[sm_port_count-1].external_reference_designator_s = smbios_get_string(location, sm_port[sm_port_count-1].external_reference_designator);
}

static void parse_smbios_cpu(uint32_t location)
{
    copy_memory((uint8_t*)&sm_cpu[sm_cpu_count-1], (uint8_t*)(location + 4), (uint32_t)(sm_hdr.length - 4));
    sm_socket_count = (uint8_t)max((uint32_t)sm_socket_count, (uint32_t)sm_cpu[sm_cpu_count-1].socket);
    
    location += sm_hdr.length;
    
    sm_cpu[sm_cpu_count-1].socket_s = smbios_get_string(location, sm_cpu[sm_cpu_count-1].socket);
    sm_cpu[sm_cpu_count-1].manufacturer_s = smbios_get_string(location, sm_cpu[sm_cpu_count-1].manufacturer);
    sm_cpu[sm_cpu_count-1].version_s = smbios_get_string(location, sm_cpu[sm_cpu_count-1].version);
    sm_cpu[sm_cpu_count-1].serial_s = smbios_get_string(location, sm_cpu[sm_cpu_count-1].serial);
    sm_cpu[sm_cpu_count-1].asset_s = smbios_get_string(location, sm_cpu[sm_cpu_count-1].asset);
    sm_cpu[sm_cpu_count-1].part_s = smbios_get_string(location, sm_cpu[sm_cpu_count-1].part);
 
    /*
    if(sm_cpu[sm_cpu_count-1].current_speed){
    printf("    speed = %uMHz\n", sm_cpu[sm_cpu_count-1].current_speed);}
    if(sm_cpu[sm_cpu_count-1].socket_s){
    printf("    socket = %s \n", sm_cpu[sm_cpu_count-1].socket_s);}   
    if(sm_cpu[sm_cpu_count-1].manufacturer_s){
    printf("    manufacturer = %s \n", sm_cpu[sm_cpu_count-1].manufacturer_s);}
    if(sm_cpu[sm_cpu_count-1].version_s){
    printf("    version = %s \n", sm_cpu[sm_cpu_count-1].version_s);}
    if(sm_cpu[sm_cpu_count-1].serial_s){
    printf("    serial = %s \n", sm_cpu[sm_cpu_count-1].serial_s);}
    if(sm_cpu[sm_cpu_count-1].asset_s){
    printf("    asset = %s \n", sm_cpu[sm_cpu_count-1].asset_s);}
    if(sm_cpu[sm_cpu_count-1].part_s){
    printf("    part = %s \n", sm_cpu[sm_cpu_count-1].part_s);}
    */
}

static void parse_smbios_chassis(uint32_t location)
{
    copy_memory((uint8_t*)&sm_chas[sm_chas_count-1], (uint8_t*)(location + 4), (uint32_t)(sm_hdr.length - 4));
    
    location += sm_hdr.length;
    
    sm_chas[sm_chas_count-1].manufacturer_s = smbios_get_string(location, sm_chas[sm_chas_count-1].manufacturer);
    sm_chas[sm_chas_count-1].version_s = smbios_get_string(location, sm_chas[sm_chas_count-1].version);
    sm_chas[sm_chas_count-1].serial_s = smbios_get_string(location, sm_chas[sm_chas_count-1].serial);
    sm_chas[sm_chas_count-1].asset_s = smbios_get_string(location, sm_chas[sm_chas_count-1].asset);
    
    /*
    if(sm_chas[sm_chas_count-1].manufacturer_s){
    printf("    manufacturer = %s \n", sm_chas[sm_chas_count-1].manufacturer_s);}
    if(sm_chas[sm_chas_count-1].version_s){
    printf("    version = %s \n", sm_chas[sm_chas_count-1].version_s);}
    if(sm_chas[sm_chas_count-1].serial_s){
    printf("    serial = %s \n", sm_chas[sm_chas_count-1].serial_s);}
    if(sm_chas[sm_chas_count-1].asset_s){
    printf("    asset = %s \n", sm_chas[sm_chas_count-1].asset_s);}
    */
}

static void parse_smbios_module(uint32_t location)
{
    copy_memory((uint8_t*)&sm_mod[sm_mod_count-1], (uint8_t*)(location + 4), (uint32_t)(sm_hdr.length - 4));
    
    location += sm_hdr.length;
    
    sm_mod[sm_mod_count-1].manufacturer_s = smbios_get_string(location, sm_mod[sm_mod_count-1].manufacturer);
    sm_mod[sm_mod_count-1].product_s = smbios_get_string(location, sm_mod[sm_mod_count-1].product);
    sm_mod[sm_mod_count-1].version_s = smbios_get_string(location, sm_mod[sm_mod_count-1].version);
    sm_mod[sm_mod_count-1].serial_s = smbios_get_string(location, sm_mod[sm_mod_count-1].serial);
    sm_mod[sm_mod_count-1].asset_s = smbios_get_string(location, sm_mod[sm_mod_count-1].asset);
    sm_mod[sm_mod_count-1].location_s = smbios_get_string(location, sm_mod[sm_mod_count-1].location);
    
    /*
    if(sm_mod[sm_mod_count-1].manufacturer_s){
    printf("    manufacturer = %s \n", sm_mod[sm_mod_count-1].manufacturer_s);}
    if(sm_mod[sm_mod_count-1].product_s){
    printf("    product = %s \n", sm_mod[sm_mod_count-1].product_s);}
    if(sm_mod[sm_mod_count-1].version_s){
    printf("    version = %s \n", sm_mod[sm_mod_count-1].version_s);}
    if(sm_mod[sm_mod_count-1].serial_s){
    printf("    serial = %s \n", sm_mod[sm_mod_count-1].serial_s);}
    if(sm_mod[sm_mod_count-1].asset_s){
    printf("    asset = %s \n", sm_mod[sm_mod_count-1].asset_s);}
    if(sm_mod[sm_mod_count-1].location_s){
    printf("    location = %s \n", sm_mod[sm_mod_count-1].location_s);}
    */
}

static void parse_smbios_system(uint32_t location)
{
    copy_memory((uint8_t*)&sm_sys[sm_sys_count-1], (uint8_t*)(location + 4), (uint32_t)(sm_hdr.length - 4));
    
    location += sm_hdr.length;
    
    sm_sys[sm_sys_count-1].manufacturer_s = smbios_get_string(location, sm_sys[sm_sys_count-1].manufacturer);
    sm_sys[sm_sys_count-1].product_s = smbios_get_string(location, sm_sys[sm_sys_count-1].product);
    sm_sys[sm_sys_count-1].version_s = smbios_get_string(location, sm_sys[sm_sys_count-1].version);
    sm_sys[sm_sys_count-1].serial_s = smbios_get_string(location, sm_sys[sm_sys_count-1].serial);
    sm_sys[sm_sys_count-1].sku_s = smbios_get_string(location, sm_sys[sm_sys_count-1].sku);
    sm_sys[sm_sys_count-1].family_s = smbios_get_string(location, sm_sys[sm_sys_count-1].family);
    
    /*
    if(sm_sys[sm_sys_count-1].manufacturer_s){
    printf("    manufacturer = %s \n", sm_sys[sm_sys_count-1].manufacturer_s);}
    if(sm_sys[sm_sys_count-1].product_s){
    printf("    product = %s \n", sm_sys[sm_sys_count-1].product_s);}
    if(sm_sys[sm_sys_count-1].version_s){
    printf("    version = %s \n", sm_sys[sm_sys_count-1].version_s);}
    if(sm_sys[sm_sys_count-1].serial_s){
    printf("    serial = %s \n", sm_sys[sm_sys_count-1].serial_s);}
    if(sm_sys[sm_sys_count-1].sku_s){
    printf("    sku = %s \n", sm_sys[sm_sys_count-1].sku_s);}
    if(sm_sys[sm_sys_count-1].family_s){
    printf("    family = %s \n", sm_sys[sm_sys_count-1].family_s);}
    */
}

static void parse_smbios_bios(uint32_t location)
{
    copy_memory((uint8_t*)&sm_bios, (uint8_t*)(location + 4), (uint32_t)(sm_hdr.length - 4));
    
    location += sm_hdr.length;
    
    sm_bios.vendor_s = smbios_get_string(location, sm_bios.vendor);
    sm_bios.version_s = smbios_get_string(location, sm_bios.version);
    sm_bios.release_date_s = smbios_get_string(location, sm_bios.release_date);
    
    /*
    if(sm_bios.vendor_s){
    printf("    vendor = %s \n", sm_bios.vendor_s);}
    if(sm_bios.version_s){
    printf("    version = %s \n", sm_bios.version_s);}
    if(sm_bios.release_date_s){
    printf("    release date = %s \n", sm_bios.release_date_s);}
    */
}

static void parse_structures(uint32_t location)
{        
    sm_bios_count = sm_sys_count = 0;
    sm_mod_count = sm_chas_count = 0;
    sm_cpu_count = sm_memdev_count = 0;
    sm_port_count = sm_slot_count = 0;
    
    sm_bsp_id = sm_memdev_filled = 0;

    for(sm_structure_count = 0; sm_structure_count < 64; sm_structure_count++)
    {
        sm_structures[sm_structure_count].address = 0;
        sm_structures[sm_structure_count].type = 0;
    }  
    
    sm_structure_count = 0;
    
    while(1)
    {
        sm_hdr.type = *(uint8_t*)location;
        sm_hdr.length = *(uint8_t*)(location + 1);
        sm_hdr.handle = *(uint16_t*)(location + 2);
        
        sm_structures[sm_structure_count].address = location;
        sm_structures[sm_structure_count++].type = sm_hdr.type;
        
        switch(sm_hdr.type)
        {
            case 0:
                // specs say that there should only be one BIOS structure
                //printf("SMBIOS: BIOS: \n");
                if(sm_bios_count >= 1){break;}else{sm_bios_count++;}
                parse_smbios_bios(location);
                break;
                
            case 1:  
                //printf("SMBIOS: SYSTEM: \n");
                if(sm_sys_count >= 4){break;}else{sm_sys_count++;}
                parse_smbios_system(location);
                break;
                
            case 2:  
                //printf("SMBIOS: MODULE: \n");
                if(sm_mod_count >= 8){break;}else{sm_mod_count++;}
                parse_smbios_module(location);
                break;
                
            case 3:
                //printf("SMBIOS: CHASSIS: \n");
                if(sm_chas_count >= 4){break;}else{sm_chas_count++;}
                parse_smbios_chassis(location);
                break;
                
            case 4:
                //printf("SMBIOS: CPU: \n");
                if(sm_cpu_count >= 64){break;}else{sm_cpu_count++;}
                parse_smbios_cpu(location);
                break;
                
            case 8:
                //printf("SMBIOS: PORT: \n");
                if(sm_port_count >= 64){break;}else{sm_port_count++;}
                parse_smbios_port(location);
                break;
                
            case 9:
                //printf("SMBIOS: SLOT: \n");
                if(sm_slot_count >= 64){break;}else{sm_slot_count++;}
                parse_smbios_slot(location);
                break;
                
            case 17:
                //printf("SMBIOS: MEMORY DEVICE: \n");
                if(sm_memdev_count >= 64){break;}else{sm_memdev_count++;}
                parse_smbios_memdev(location);
                break;
            
            case 127:
                //for(;;);
                return;
            
            default:
                //printf("SMBIOS: structure(%u): \n", sm_hdr.type);
                break;
        }
        
        location += sm_hdr.length;
        while(*(uint16_t*)location++ != 0x0000){}
        location++;
    }
    
}

static void parse_eps(uint32_t location)
{
    copy_memory((uint8_t*)&sm_eps, (uint8_t*)location, sizeof(sm_eps));
    
    if(smbios_checksum(location, sm_eps.length))
    {printf("SMBIOS: EPS checksum is incorrect \n"); return;}
    
    if(sm_eps.major_version == 0x02 && sm_eps.minor_version == 0x01)
    {
        if(sm_eps.length == 0x1E)
        {printf("SMBIOS: known bug found (sm_eps.length == 0x1E) \n");}
        else if(sm_eps.length != 0x1F)
        {printf("SMBIOS: incorrect EPS length defined \n"); return;}
    }
    else
    {
        if(sm_eps.length != 0x1F)
        {printf("SMBIOS: incorrect EPS length defined \n"); return;}
    }
    
    if(compare_memory((uint8_t*)(location + 0x10), (uint8_t*)"_DMI_", 5))
    {printf("SMBIOS: IEPS anchor string is incorrect \n"); return;}
    
    if(smbios_checksum(location + 0x10, 0x0F))
    {printf("SMBIOS: IEPS checksum is incorrect \n"); return;}
    
    smbios_present = true;
    printf("SMBIOS: version %u.%u \n", sm_eps.major_version, sm_eps.minor_version);
    
    parse_structures(sm_eps.struct_table_address);
}

uint32_t get_smbios_memory_count(void)
{
    uint32_t i, count;
    
    count = 0;
    for(i = 0; i < sm_memdev_count; i++)
    {
        count += sm_memdev[i].size;
    }

    return count;
}

static void cleanup_task(void)
{
    kill_task(active_task_id);
}

void parse_smbios(void)
{
    uint32_t i;
    
    smbios_parsing_completed = false;
    smbios_present = false;
    
    for(i = 0xF0000; i < 0x100000; i += 16)
    {
        if(!compare_memory((uint8_t*)i, (uint8_t*)"_SM_", 4))
        {
            printf("SMBIOS: EPS found at %x \n", i);
            parse_eps(i);
            smbios_parsing_completed = true;
            cleanup_task();
        }
        GUTS();
    }
    smbios_parsing_completed = true;
    
    printf("SMBIOS: structure entry point was not found, skipping... \n");
    cleanup_task();
}

